use std::{mem::MaybeUninit, time::Duration};

use jni::objects::GlobalRef;
use log::{debug, error};
use tokio::{
    sync::{mpsc, Mutex},
    time::sleep,
};

use crate::{
    error::{new_err, AppErr},
    utils::global::get_rt,
    SERVER_HOST, SERVER_TCP,
};

use super::conn::{new_conn, SharedConn};

static mut MANAGER: MaybeUninit<ConnManager> = MaybeUninit::uninit();

pub fn init(cb: GlobalRef) {

    let (tx, rx) = mpsc::channel(10);
    let manager = ConnManager {
        tx,
        conn: Mutex::const_new(None),
        cb,
    };
    unsafe {
        MANAGER.write(manager);
    }
    get_rt().spawn(manager_loop(rx));
    get_manager().connect();
}

#[inline]
pub fn get_manager() -> &'static ConnManager {
    unsafe { MANAGER.assume_init_ref() }
}

pub async fn get_connect() -> Result<SharedConn, AppErr> {
    let conn = get_manager()
        .conn
        .lock()
        .await
        .as_ref()
        .ok_or(new_err("没有连接"))?
        .clone();
    Ok(conn)
}

enum CtrlCmd {
    Reconnect,
    PingReset,
}

pub struct ConnManager {
    tx: mpsc::Sender<CtrlCmd>,
    conn: Mutex<Option<SharedConn>>,
    cb: GlobalRef,
}

impl ConnManager {

    pub fn connect(&self) {
        _ = self.tx.try_send(CtrlCmd::Reconnect);
    }
}

async fn manager_loop(mut rx: mpsc::Receiver<CtrlCmd>) {
    debug!("manager loop start");
    loop {
        let ret = tokio::select! {
            cmd = rx.recv() => {
                if let Some(cmd) = cmd {
                    do_cmd(cmd).await
                } else {
                    break;
                }
            },

            _ = sleep(Duration::from_secs(3)) => {
                do_ping().await
            }
        };

        if let Err(e) = ret {
            error!("manager:{}", e);
        }
    }

    debug!("manager loop exit");
}

async fn do_cmd(cmd: CtrlCmd) -> Result<(), AppErr> {
    match cmd {
        CtrlCmd::Reconnect => {
            do_conn().await?;
        }
        CtrlCmd::PingReset => {}
    };
    Ok(())
}

async fn do_conn() -> Result<(), AppErr> {
    debug!("do connect");

    let mut p = get_manager().conn.lock().await;
    *p = None;

    let addr = format!("{}:{}", SERVER_HOST, SERVER_TCP);
    let conn = new_conn(&addr, get_manager().cb.clone()).await?;

    debug!("connect success");

    *p = Some(conn);

    drop(p);

    let info = &get_connect().await?.info;
    debug!("login:{:?}", info);

    Ok(())
}

async fn do_ping() -> Result<(), AppErr> {
    debug!("do ping");

    let conn = match get_connect().await {
        Ok(conn) => conn,
        Err(e) => {
            get_manager().connect();
            return Err(e);
        }
    };
    
    if let Err(e) = conn.ping().await {
        get_manager().connect();
        return Err(e);
    }

    Ok(())
}


